package com.springframework.aop.fremework;

import com.springframework.aop.service.AdvisedSupport;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成，因此被代理类不需要实现任何接口。
 * 关于扩展进去的用户拦截方法，主要是在 Enhancer#setCallback 中处理，用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。
 */
public class Cglib2AopProxy implements AopProxy {
    private final AdvisedSupport advisedSupport;

    public Cglib2AopProxy(AdvisedSupport advisedSupport) {
        this.advisedSupport = advisedSupport;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(new DynamicMethodProxy(advisedSupport));
        enhancer.setSuperclass(advisedSupport.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(advisedSupport.getTargetSource().getTargetClass());
        return enhancer.create();
    }

    private static class DynamicMethodProxy implements MethodInterceptor {
        private final AdvisedSupport advised;

        public DynamicMethodProxy(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            CglibMethodProxt cglibMethodProxt = new CglibMethodProxt(advised.getTargetSource().getTarget(), method, args, proxy);
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.getMethodInterceptor().invoke(cglibMethodProxt);
            }
            return cglibMethodProxt.proceed();
        }
    }

    private static class CglibMethodProxt extends RelectMethodInvocation {
        private final MethodProxy methodProxy;
        private Object target;
        private Object[] arguments;

        public CglibMethodProxt(Object target, Method method, Object[] arguments, MethodProxy methodProxy) {
            super(target, method, arguments);
            this.methodProxy = methodProxy;
            this.target = target;
            this.arguments = arguments;
        }

        @Override
        public Object proceed() throws Throwable {
            return this.getMethod().invoke(this.target, this.arguments);
        }
    }
}
